#include "misc/errors.hpp"
#include "systems/logger.hpp"
#include "systems/scripting.hpp"
#include "systems/object_manager.hpp"

#include "ogre_render.hpp"
#include "ogre_logger.hpp"
#include "ogre_scene_node.hpp"

#include <OgreRoot.h>
#include <OgreRenderSystem.h>
#include <OgreRenderWindow.h>
#include <OgreWindowEventUtilities.h>
#include <OgreEntity.h>

#include <OgreMeshManager.h>

#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>

using namespace std;

scalar_t s_per_phisics_scale = 2;

render_impl_t::render_impl_t(const config_t &config) :
    ogre_logger_(new ogre_logger_t), ogre_root_(new Ogre::Root("", ""))
{
    //load plugins
    string plugins_folder;

# if _WIN32
    plugins_folder += ".\\";
# else
    plugins_folder += "/usr/local/lib/OGRE/";
# endif

    //load plugins
    BOOST_FOREACH(const config_t::opt_t::value_type &param, config.tree.get_child("Ogre.Plugins"))
                {
                    string plugin_name(param.second.data());

# ifdef _WIN32
#  ifdef _DEBUG
                    plugin_name += "_d";
#  endif
# endif
                    try
                    {
                        ogre_root_->loadPlugin(plugins_folder + plugin_name);
                    }
                    catch (const Ogre::Exception &e)
                    {
                        logger::log(CRITICAL, e.getDescription());
                    }
                }

    //get available renders
    const Ogre::RenderSystemList& render_system_list = ogre_root_->getAvailableRenderers();

    Ogre::RenderSystem *render_system_ = NULL;
    string desirable_render = config.tree.get<string> ("Ogre.Render.RenderSystem");

    logger::log(DEBUG) << "[Render] Render systems found:";
    BOOST_FOREACH(Ogre::RenderSystem *render, render_system_list)
                {
                    if (desirable_render == render->getName())
                    {
                        render_system_ = render;
                    }
                    logger::log(DEBUG) << "[Render] Render system \"" << render->getName() << "\" found.";
                }

    if (render_system_list.size() == 0)
    {
        logger::log(CRITICAL, "[Render] No render system was found.");
        throw runtime_error("no render system was found");
    }

    if (render_system_ == NULL)
    {
        logger::log(TRACE) << "[Render] Desirable render not found, use first of available";
        render_system_ = render_system_list[0];
    }

    logger::log(TRACE) << "[Render] Use \"" << render_system_->getName() << "\" render system.";
    ogre_root_->setRenderSystem(render_system_);

    //initialize ogre root
    ogre_root_->initialise(false, "", "");

    //read other params from config file
    size_x_ = config.tree.get<size_t> ("Ogre.Render.SizeX");
    size_y_ = config.tree.get<size_t> ("Ogre.Render.SizeY");

    full_screen_ = config.tree.get<string> ("Ogre.Render.FullScreen") == "true";

    vertical_sync_ = config.tree.get<string> ("Ogre.Render.vsync") == "true";

    // fill render_window_params_ with additional params
    //

    //setup resources location
    logger::log(TRACE) << "[Render] Setup resources locations.";
    Ogre::ResourceGroupManager &res_gr_mgr = Ogre::ResourceGroupManager::getSingleton();

    BOOST_FOREACH(const config_t::opt_t::value_type &res_location, config.tree.get_child("Ogre.Resources"))
                {
                    res_gr_mgr.addResourceLocation(res_location.second.data(), res_location.first, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true);
                }

    res_gr_mgr.initialiseResourceGroup(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
    //res_gr_mgr.loadResourceGroup(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

    scene_type_ = config.tree.get<string> ("Ogre.Render.SceneType");

}

render_impl_t::~render_impl_t()
{
}

void render_impl_t::prepare_render()
{
    Ogre::NameValuePairList pairs;

    BOOST_FOREACH(const render_par_map_t::value_type &i, render_window_params_)
                {
                    pairs[i.first] = i.second;
                }

    render_window_ = ogre_root_->createRenderWindow("", size_x_, size_y_, full_screen_, &pairs);
    render_window_->setActive(true);
    render_window_->setAutoUpdated(false);

    /*
     *    "DefaultSceneManager"
     *    "BspSceneManager"
     *    "OctreeSceneManager"
     *    "TerrainSceneManager"
     *    "PCZSceneManager"
     */
    logger::log(DEBUG) << "[Render] Create scene with type: " << scene_type_;
    scene_manager_ = ogre_root_->createSceneManager(scene_type_, "default");
    root_node_.reset(new ogre_scene_node_t(scene_manager_->getRootSceneNode(), 0));

    Ogre::Plane plane(Ogre::Vector3::UNIT_Y, 0);

    Ogre::MeshManager::getSingleton().createPlane("ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500, 1500, 20, 20, true, 1, 5, 5, Ogre::Vector3::UNIT_Z);

    Ogre::Entity* entGround = scene_manager_->createEntity("GroundEntity", "ground");
    scene_manager_->getRootSceneNode()->createChildSceneNode()->attachObject(entGround);

    //entGround->setMaterialName("Examples/Rockwall");
}
void render_impl_t::cleanup_render()
{
    render_window_->removeAllViewports();
    scene_manager_->destroyAllCameras();
    scene_manager_->destroyAllManualObjects();
    scene_manager_->destroyAllEntities();
    scene_manager_->getRootSceneNode()->removeAndDestroyAllChildren();
}

boost::tuple<boost::shared_ptr<scene_node_t>, vector3_t> render_impl_t::create_entity(object_class_t &obj_class)
{
    Ogre::ResourceGroupManager &res_gr_mgr = Ogre::ResourceGroupManager::getSingleton();

    if (!obj_class.is_resources_loaded_)
    {
        //load render resources
        BOOST_FOREACH( const string &res_name, obj_class.texture_)
                    {
                        res_gr_mgr.declareResource(res_name, "Texture", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
                    }
        BOOST_FOREACH( const string &res_name, obj_class.mesh_)
                    {
                        res_gr_mgr.declareResource(res_name, "Mesh", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
                    }

        obj_class.is_resources_loaded_ = true;
    }

    res_gr_mgr.initialiseResourceGroup(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

    Ogre::SceneNode *node = scene_manager_->getRootSceneNode()->createChildSceneNode();

    //    typedef const std::pair<std::string, std::string> str_pair_t;
    //    BOOST_FOREACH( str_pair_t &struct_pair, obj_class.structure_ )
    //                {
    //    Ogre::Entity *entity = scene_manager_->createEntity(struct_pair.first);
    //    entity->setMaterialName(struct_pair.second, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
    //    node->attachObject(entity);
    //                }

    // just one entity per object now
    Ogre::Entity *entity = scene_manager_->createEntity(obj_class.structure_[0].first);
    entity->setMaterialName(obj_class.structure_[0].second, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

    node->attachObject(entity);

    Ogre::AxisAlignedBox ogre_box = entity->getBoundingBox();

    boost::shared_ptr<scene_node_t> node_ptr(new ogre_scene_node_t(node, ogre_box.getSize().y / 2.f / s_per_phisics_scale));

    vector3_t bounding_box(ogre_box.getSize().x, ogre_box.getSize().y, ogre_box.getSize().z);

    return boost::tuple<boost::shared_ptr<scene_node_t>, vector3_t>(node_ptr, bounding_box);
}

scalar_t render_impl_t::get_per_phisics_scale()
{
    return s_per_phisics_scale;
}

void render_impl_t::render_one_frame()
{

    if (render_window_->isClosed())
    {
        throw boost::thread_interrupted();
    }

    render_window_->update(false);

    render_window_->swapBuffers(vertical_sync_);

    ogre_root_->renderOneFrame();

    Ogre::WindowEventUtilities::messagePump();
}

/* --- API --- */
/* {@ */

//camera
void render_impl_t::create_camera(const std::string &name)
{
    logger::log(DEBUG) << "[Script] Create camera: " << name;
    camera_ = scene_manager_->createCamera(name);
    camera_node_ = scene_manager_->getRootSceneNode()->createChildSceneNode(name);
    camera_node_->attachObject(camera_);
    view_port_ = render_window_->addViewport(camera_);
    view_port_->setAutoUpdated(true);
    float ratio = float(view_port_->getActualWidth()) / float(view_port_->getActualHeight());
    camera_->setAspectRatio(ratio);
}

void render_impl_t::set_back_color(const color_t &color)
{
    logger::log(DEBUG) << "[Script] Set viewport background color: " << color.r << " " << color.g << " " << color.b << " " << color.alpha;
    view_port_->setBackgroundColour(Ogre::ColourValue(color.r, color.g, color.b, color.alpha));
}

void render_impl_t::set_near_clip_dist(float dist)
{
    logger::log(DEBUG) << "[Script] Set camera near clip distance: " << dist;
    camera_->setNearClipDistance(dist);
}

void render_impl_t::set_far_clip_dist(float dist)
{
    logger::log(DEBUG) << "[Script] Set camera far clip distance: " << dist;
    camera_->setFarClipDistance(dist);
}
void render_impl_t::set_camera(const vector3_t &pos)
{
    logger::log(DEBUG) << "[Script] Set camera to: " << pos.x << " " << pos.y << " " << pos.z;
    camera_->setPosition(pos.x, pos.y, pos.z);
}

void render_impl_t::camera_look_at(const vector3_t &dir)
{
    logger::log(DEBUG) << "[Script] Set camera look to: " << dir.x << " " << dir.y << " " << dir.z;
    camera_->lookAt(dir.x, dir.y, dir.z);
}

//light
void render_impl_t::set_ambient_light(const color_t &color)
{
    logger::log(DEBUG) << "[Script] Set scene ambient light: " << color.r << " " << color.g << " " << color.b << " " << color.alpha;
    scene_manager_->setAmbientLight(Ogre::ColourValue(color.r, color.g, color.b, color.alpha));
}

void render_impl_t::create_directional_light(const color_t &diffuse_color, const color_t &specular_color, const vector3_t &direction)
{
    logger::log(DEBUG) << "[Script] Create directional light: " << diffuse_color.r << " " << diffuse_color.g << " " << diffuse_color.b;
    Ogre::Light* light = scene_manager_->createLight();

    light->setType(Ogre::Light::LT_DIRECTIONAL);

    light->setDiffuseColour(diffuse_color.r, diffuse_color.g, diffuse_color.b);
    light->setSpecularColour(specular_color.r, specular_color.g, specular_color.b);
    light->setDirection(direction.x, direction.y, direction.z);

    Ogre::SceneNode *light_node = scene_manager_->getRootSceneNode()->createChildSceneNode();
    light_node->attachObject(light);
}

/* @} */
